<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge,chrome=1">
  <title>Document</title>
  <script src="./library/jquery-3.6.0.js"></script>
  <script src="./library/vue-2.6.14.js"></script>
  <script src="../Library/layer-v3.5.1/layer/layer.js"></script>
  <link rel="stylesheet" type="text/css" href="./index.css">
  <link rel="stylesheet" type="text/css" href="./library/iconfont/iconfont.css">
  <style>
    .iconfont {
      font-size: 22px;
      color: #456;
    }

    .highlight {
      background-color: #00000032;
    }

    .if-l {
      font-size: 24px
    }

    .es-richText .HtmlView {
      font-weight: 400;
      font-size: 15px;
    }

    [v-cloak] {
      display: none;
    }

    .barArea {
      margin: 30px;
      display: flex;
      align-items: center;
      font-size: 14px;
    }

    .fnBtn {
      border: 1px #666 solid;
      border-radius: 6px;
      margin-right: 24px;
      padding: 3px 6px;
    }

    input[type=file] {
      border: 1px #999 solid
    }

    input[type=file]:active,
    input[type='file']:focus {
      outline: none;
    }

    .fontSizeSel {
      width: 50px;
    }

    .masker {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
    }
  </style>
</head>

<body>
  <div id="app" style="margin-bottom: 1000px;">
    <div v-for="(item,index) in dataArr" :key="item.type+item.id" v-cloak>{{item.type+item.id}}
      <!-- text -->
      <div class="barArea" v-if="item.type === 'text'">
        <div class="es-richText" :class="'rich-'+item.id" style="width: 750px;">
          <div class="toolbar" :listid="item.id" style="display: none;">
            <div class="toHtml_btn" onclick="esEditor.toHtml($(this))" style="font-size:12px;" title="切换为 '代码编辑'">
              <i class="iconfont icon-code if-l"></i>
            </div>
            <div class="toView_btn" onclick="esEditor.toView($(this))" style="font-size:12px;display:none;" title="切换为 '可视化编辑'">
              <i class="iconfont icon-all_view"></i>
            </div>
            <div class="htmlHide" title="字体大小">
              <i class="iconfont icon-font-size-"></i>
              <select class="fontSizeSel" onchange="excmd.fontSize(esEditor.doc, $(this).val())">
                <option value="1">1&emsp;&nbsp;12px</option>
                <option value="2">2&emsp;&nbsp;13px</option>
                <option value="3" selected>3&emsp;&nbsp;16px</option>
                <option value="4">4&emsp;&nbsp;18px</option>
                <option value="5">5&emsp;&nbsp;24px</option>
                <option value="6">6&emsp;&nbsp;32px</option>
                <option value="7">7&emsp;&nbsp;48px</option>
              </select>
            </div>
            <div class="htmlHide" onclick="excmd.justifyFull(esEditor.doc);esEditor.changeAlign('j')" cmd="align" mode="j" title="两端对齐">
              <i class="iconfont icon-align-justify"></i>
            </div>
            <div class="htmlHide" onclick="excmd.justifyLeft(esEditor.doc);esEditor.changeAlign('l')" cmd="align" mode="l" title="左对齐">
              <i class="iconfont icon-align-left"></i>
            </div>
            <div class="htmlHide" onclick="excmd.justifyCenter(esEditor.doc);esEditor.changeAlign('c')" cmd="align" mode="c" title="居中">
              <i class="iconfont icon-align-center"></i>
            </div>
            <div class="htmlHide" onclick="excmd.justifyRight(esEditor.doc);esEditor.changeAlign('r')" cmd="align" mode="r" title="右对齐">
              <i class="iconfont icon-align-right"></i>
            </div>
            <div class="htmlHide" onclick="esEditor.foreColor()" title="字体颜色">
              <i class="iconfont icon-font-color"></i>
              <input class="foreColor" onclick="esEditor.openMasker(event)" onchange="esEditor.foreColor()" type="color" />
            </div>
            <div class="htmlHide" onclick="esEditor.hiliteColor()" title="字体背景">
              <i class="iconfont icon-hilitecolor"></i>
              <input class="hiliteColor" onclick="esEditor.openMasker(event)" onchange="esEditor.hiliteColor()" type="color" />
            </div>
            <div class="htmlHide" onclick="excmd.bold(esEditor.doc)" title="粗体">
              <i class="iconfont icon-bold"></i>
            </div>
            <div class="htmlHide" onclick="excmd.italic(esEditor.doc)" title="斜体">
              <i class="iconfont icon-italic"></i>
            </div>
            <div class="htmlHide" onclick="excmd.underline(esEditor.doc)" title="下划线">
              <i class="iconfont icon-underline"></i>
            </div>
            <div class="htmlHide" onclick="excmd.strikeThrough(esEditor.doc)" title="删除线">
              <i class="iconfont icon-strikethrough"></i>
            </div>
            <div class="htmlHide" onclick="excmd.insertText(esEditor.doc,HTMLDecode('&emsp;&emsp;'))" title="两格缩进 (EMSP)">
              <i class="iconfont icon-space"></i>
            </div>

            <div class="htmlHide" onclick="excmd.undo(esEditor.doc)" title="撤销">
              <i class="iconfont icon-undo if-l"></i>
            </div>
            <div class="htmlHide" onclick="excmd.redo(esEditor.doc)" title="重做">
              <i class="iconfont icon-redo if-l"></i>
            </div>
            <div class="htmlHide" onclick="excmd.superscript(esEditor.doc)" title="上角标">
              <i class="iconfont icon-superscript"></i>
            </div>
            <div class="htmlHide" onclick="excmd.subscript(esEditor.doc)" title="下角标">
              <i class="iconfont icon-subscript"></i>
            </div>
            <div class="htmlHide" onclick="excmd.insertHorizontalRule(esEditor.doc)" title="插入水平线">
              <i class="iconfont icon-separator"></i>
            </div>
            <div class="htmlHide" onclick="esEditor.lineHeight()" title="行间距">
              <i class="iconfont icon-line-height"></i>
            </div>
            <div class="htmlHide" onclick="excmd.insertOrderedList(esEditor.doc)" title="有序列表">
              <i class="iconfont icon-list-ordered"></i>
            </div>
            <div class="htmlHide" onclick="excmd.insertUnorderedList(esEditor.doc)" title="无序列表">
              <i class="iconfont icon-list-unordered"></i>
            </div>
            <div class="htmlHide" onclick="excmd.indent(esEditor.doc)" title="缩进">
              <i class="iconfont icon-indent-increase"></i>
            </div>
            <div class="htmlHide" onclick="excmd.outdent(esEditor.doc)" title="取消缩进">
              <i class="iconfont icon-indent-decrease"></i>
            </div>
            <div class="htmlHide" onclick="esEditor.createLink()" title="超链接">
              <i class="iconfont icon-link"></i>
            </div>
            <div class="htmlHide" onclick="excmd.unlink(esEditor.doc)" title="取消链接">
              <i class="iconfont icon-link-unlink-m"></i>
            </div>
          </div>
          <div style="width:100%; height:300px; box-sizing: border-box; position: relative;">
            <div class="masker" style="display:none;" onclick="esEditor.closeMasker()"></div>
            <iframe class="HtmlEdit" style="width:100%;height:100%;"></iframe>
            <textarea class="HtmlView" onfocus="esEditor.getFocus($(this).attr('listid'))" :listid="item.id" style="width:100%;height:100%; display:none;"></textarea>
          </div>
        </div>
        text<div @click="delData(index)">-删除</div>
      </div>
      <!-- image -->
      <div class="barArea" v-else-if="item.type === 'image'">
        <input type="file" accept="image/gif,image/jpeg,image/png" @change="changeFn(item, index, $event)" />
        image<div @click="delData(index)">-删除</div>
      </div>
      <!-- audio -->
      <div class="barArea" v-else-if="item.type === 'audio'">
        <input type="file" accept="audio/*" @change="changeFn(item, index, $event)" />
        audio<div @click="delData(index)">-删除</div>
      </div>
      <!-- video -->
      <div class="barArea" v-else-if="item.type === 'video'">
        <input type="file" accept="video/*" @change="changeFn(item, index, $event)" />
        video<div @click="delData(index)">-删除</div>
      </div>
    </div>
    {{dataArr}}
    <div class="barArea">
      <div class="fnBtn" @click="addData('text')">+文字</div>
      <div class="fnBtn" @click="addData('image')">+图片</div>
      <div class="fnBtn" @click="addData('audio')">+音频</div>
      <div class="fnBtn" @click="addData('video')">+视频</div>
    </div>
    <div class="barArea">
      <div class="fnBtn" onclick="esEditor.saveAll()">保存</div>
      <div class="fnBtn" @click="examine()">提交</div>
    </div>
  </div>
  <!-- 运行库 -->
  <script src="./library/execCommand.js"></script>
  <script>
    var loader;
    window.esEditor = {
      activeID: undefined,
      editor: "", // ? 获取iframe Window 对象
      doc: "", // ! 获取iframe documen 对象
      getDataInit: function (dataList) { // 修改文章请求数据
        var nth = 0;
        dataList.forEach(function (item) {
          if (item.type == "text") {
            var iframeElem = $(".rich-" + item.id).find(".HtmlEdit").get(0);
            var _editor = iframeElem.contentWindow;
            var _doc = iframeElem.contentDocument;
            _editor.document.designMode = 'On'; //打开设计模式
            _editor.document.contentEditable = true; // 设置元素为可编辑
            _doc.write(HTMLDecode("&lt;script src='../Library/jquery/jquery-3.6.0.js'&gt;&lt;/script&gt;"));
            _doc.write(item.content);
            _doc.write(HTMLDecode(`&lt;script&gt;
              var listId = ` + item.id + `;
              // 点击事件 设置编辑器选中态
              $(document).on("mousedown", function () {
                window.parent.esEditor.getFocus(listId);
              })
              // 点击事件 设置文字框选态（存在问题）
              $(document).on("mouseup", function (e) {
                window.parent.esEditor.fixSel($(e.target),listId);
              })
              // 阻止Ctrl+S
              document.addEventListener("keydown", function (e) {
                if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) { // 阻止Ctrl+S，兼容Mac
                  e.preventDefault();
                  window.parent.esEditor.saveAll();
                  return false;
                }
              });
            &lt;/script&gt;`));
            _doc.getElementsByTagName("head")[0].innerHTML += HTMLDecode(`&lt;style&gt;
              p {
                word-wrap : break-word;
                word-break: break-all;
              }
            &lt;/style&gt;`)
          }
          nth++;
          if (nth == dataList.length) {
            layer.close(loader);
            this.activeID = $(".es-richText:first").find(".toolbar").attr("listid");
            if (this.activeID == undefined) return false;
            var iframeElem = $(".es-richText:first").find(".HtmlEdit").get(0);
            this.editor = iframeElem.contentWindow;
            this.doc = iframeElem.contentDocument;
            $(".es-richText:first").find(".toolbar").slideDown(300);
          }
        }.bind(this))
      },
      dataSetting: function (id) { // 新建文本域
        this.getFocus(id) // 改变当前活动文本项
        this.editor.document.designMode = 'On'; // 打开设计模式
        this.editor.document.contentEditable = true; // 设置元素为可编辑
        // 节点初始化
        this.doc.write(HTMLDecode("&lt;script src='../Library/jquery/jquery-3.6.0.js'&gt;&lt;/script&gt;"));
        this.doc.write(HTMLDecode("&lt;div class='spaceMask' style='position:fixed;top:0;left:0;right:0;bottom:0;' onmousedown='hide($(this))'&gt;&lt;/div&gt;"));
        this.doc.write(HTMLDecode(`&lt;script&gt;
          var listId = ` + id + `;
          function hide(ele) {
            window.parent.esEditor.autoFormat($(document));
          }
          // 点击事件 设置编辑器选中态
          $(document).on("mousedown", function () {
            window.parent.esEditor.getFocus(listId);
          })
          // 点击事件 设置文字框选态（存在问题）
          $(document).on("mouseup", function (e) {
            window.parent.esEditor.fixSel($(e.target),listId);
          })
          // 阻止Ctrl+S
          document.addEventListener("keydown", function (e) {
            if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) { // 阻止Ctrl+S，兼容Mac
              e.preventDefault();
              window.parent.esEditor.saveAll();
              return false;
            }
          });
        &lt;/script&gt;`));
        this.doc.getElementsByTagName("head")[0].innerHTML += HTMLDecode(`&lt;style&gt;
          p {
            word-wrap : break-word;
            word-break: break-all;
          }
        &lt;/style&gt;`)
      },
      getFocus: function (listId) { // 获取正在活动的文本域（焦点事件）
        if (this.activeID == listId) return false;
        var iframeElem = $(".rich-" + listId).find(".HtmlEdit").get(0);
        this.editor = iframeElem.contentWindow;
        this.doc = iframeElem.contentDocument;
        $(".toolbar[listid!=" + listId + "]").slideUp(300);
        $(".toolbar[listid=" + listId + "]").slideDown(300);
        this.activeID = listId;
      },
      fixSel: function (elem, listId) { // 选择事件
        // 获取字体大小
        if (elem.attr('size')) $(".rich-" + listId).find(".fontSizeSel").val(elem.attr('size'));
        else if (elem.prop('tagName') == "P") $(".rich-" + listId).find(".fontSizeSel").val(3);
        else $(".rich-" + listId).find(".fontSizeSel").val('&emsp;')
        // 获取对齐方式
        var align = elem.css("text-align");
        if (align.match('justify')) this.changeAlign('j')
        else if (align.match('left')) this.changeAlign('l')
        else if (align.match('center')) this.changeAlign('c')
        else if (align.match('right')) this.changeAlign('r')
      },
      changeAlign(mode) {
        $(".toolbar[listid=" + this.activeID + "]").find("[cmd=align]").removeClass("highlight")
        $(".toolbar[listid=" + this.activeID + "]").find("[mode=" + mode + "]").addClass("highlight")
      },
      // 格式序列化（首次点击文本框触发）
      autoFormat: function (ele) {
        ele.find("body").find(".spaceMask").remove();
        /* 节点改变监听 */
        // ele.bind("DOMNodeInserted", function (e) {
        //   setTimeout(function () {
        //     // ele.find("p").css({
        //     //   "text-indent": "2em"
        //     // })
        //   })
        // })
        setTimeout(function () {
          window.excmd.formatBlock(this.doc, 'p')
        }.bind(this))
      },
      // 单个保存自动保存（过滤多余的标签）(暂时闲置了)
      autoSave: function (activeID) {
        var iframeElem = $(".rich-" + activeID).find(".HtmlEdit").get(0);
        var _doc = iframeElem.contentDocument;
        // 导出
        vueApp.htmlRefer(this.jsFilter(_doc), activeID); // TODO
        // this.box.innerHTML = this.jsFilter()
      },
      // 差异模式保存全部（高性能）
      saveAll: function () {
        vueApp.dataArr.forEach(function (item) {
          if (item.type == "text") {
            var note = this.jsFilter($(".rich-" + item.id).find(".HtmlEdit").get(0).contentDocument);
            if (item.content != note) vueApp.htmlRefer(note, item.id);
          }
        }.bind(this))
      },
      // 过滤
      jsFilter: function (_doc_) {
        var temp = document.createElement("div");
        $(temp).html(_doc_.body.innerHTML);
        $(temp).find("script,.spaceMask").remove();
        return temp.innerHTML
      },
      toHtml: function (ele) {
        // 切换为源码视图
        $(this.doc).find(".spaceMask").trigger("mousedown")
        $(this.doc).find("body").trigger("focus")
        var cursor = ele.parents(".es-richText");
        setTimeout(function () {
          cursor.find(".HtmlEdit").hide();
          cursor.find(".HtmlView").fadeIn(300);
          cursor.find(".HtmlView").val(this.jsFilter(this.doc)); // 编辑继承
          cursor.find(".toHtml_btn").hide();
          cursor.find(".toView_btn").fadeIn(300);
          cursor.find(".htmlHide").css({
            "opacity": 0,
            "cursor": "default",
            "pointer-events": "none"
          });
        }.bind(this))
      },
      toView: function (ele) {
        var cursor = ele.parents(".es-richText");
        // 切换为效果视图
        cursor.find(".HtmlView").hide();
        cursor.find(".HtmlEdit").fadeIn(300);
        this.doc.body.innerHTML = cursor.find(".HtmlView").val()
        cursor.find(".toView_btn").hide();
        cursor.find(".toHtml_btn").fadeIn(300);
        cursor.find(".htmlHide").css({
          "opacity": 1,
          "cursor": "pointer",
          "pointer-events": "auto"
        });
      },
      openMasker(e) {
        e.stopPropagation();
        $(".masker").show();
      },
      closeMasker() {
        $(".masker").hide();
        $(this.doc).find("body").trigger("focus")
      },
      /* 编辑器API */
      foreColor: function () {
        $(this.doc).find("body").trigger("focus")
        window.excmd.foreColor(this.doc, $(".rich-" + this.activeID).find(".foreColor").val()) // 字体颜色
        setTimeout(function () {
          this.closeMasker();
        }.bind(this))
      },
      hiliteColor: function (a) {
        $(this.doc).find("body").trigger("focus")
        window.excmd.hiliteColor(this.doc, $(".rich-" + this.activeID).find(".hiliteColor").val()) // 字体背景
        setTimeout(function () {
          this.closeMasker();
        }.bind(this))
      },
      lineHeight: function () {
        $(this.doc).find("p").css("lineHeight", 2) // 行高修改
      },
      createLink: function () {
        var link = prompt("请输入链接")
        if (link != null) window.excmd.createLink(this.doc, link) // 文本链接
      }
    }

    var vueApp = new Vue({
      el: "#app",
      data: {
        id: 0,
        dataArr: [],
        upload: [],
        fakeData: [{
          "id": 1,
          "type": "text",
          "content": '<p>asdasdadsadasd asdasdasdasdasd asd asdadasdasdasd asd sdasdad</p>'
        }, {
          "id": 1,
          "type": "text",
          "content": '<p>asdasdadsadasd asdasdasdasdasd asd asdadasdasdasd asd sdasdad</p>'
        }]
      },
      mounted() { // 调试
        this.checkData(this.fakeData)
      },
      methods: {
        // 增
        addData: function (type) {
          this.dataArr.push({
            "id": this.id,
            "type": type,
            "content": ""
          });
          if (type === "text") {
            setTimeout(function () {
              window.esEditor.dataSetting(this.id);
              this.id++;
            }.bind(this))
          } else this.id++;
        },
        // 查
        checkData: function (list) {
          loader = layer.load(1)
          list.forEach(function (item, index) {
            this.dataArr.push({
              "id": index,
              "type": item.type,
              "content": item.content
            })
            this.id++
          }.bind(this))
          setTimeout(function () {
            window.esEditor.getDataInit(this.dataArr);
          }.bind(this))
        },
        // 删
        delData: function (index) {
          this.dataArr.splice(index, 1);
        },
        // 改
        changeFn: function (data, index, event) {
          // this.dataArr[index].url = 
        },
        // 上传
        htmlRefer: function (htmlStr, activeID) {
          this.dataArr.forEach(function (item) {
            if (item.id == activeID && item.type == "text") {
              item.content = htmlStr
              console.log(activeID, htmlStr);
            }
          }.bind(this))
        },
        // 处理
        examine: function () {
          window.esEditor.saveAll();
          setTimeout(function () {
            this.dataArr.forEach(function (item) {
              this.upload.push({
                "type": item.type,
                "content": item.content
              })
            }.bind(this))
            console.log(this.upload)
          }.bind(this))
        }
      }
    })

    document.addEventListener("keydown", function (e) {
      if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) { // 阻止Ctrl+S，兼容Mac
        e.preventDefault();
        window.esEditor.saveAll();
        return false;
      }
    });

    function HTMLDecode(text) {
      var temp = document.createElement("div");
      temp.innerHTML = text;
      var output = temp.innerText || temp.textContent;
      temp = null;
      return output;
    }
  </script>
</body>

</html>